package lex

import (
	"errors"
	"fmt"
	"math/big"
	"regexp"
	"showen7/num"
	"showen7/tok"
	"strconv"
	"strings"
)

type Lexer struct {
	input        string
	position     int  // current position in input (points to current char)
	readPosition int  // current reading position in input (after current char)
	ch           byte // current char under examination
	line         int  // current line number
	column       int
}

func NewLexer(input string) *Lexer {
	l := &Lexer{input: input, position: 0, readPosition: 1, line: 1, column: 1}
	if len(input) == 0 {
		l.ch = 0
	} else {
		l.ch = input[0]
	}
	return l
}

func (l *Lexer) advance() {
	if l.readPosition > len(l.input) {
		return
	}
	l.position = l.readPosition
	if l.position == len(l.input) {
		l.ch = 0
	} else {
		l.ch = l.input[l.position]
	}
	l.readPosition++
	l.column++

	// Update line and column for newlines
	if l.ch == '\n' {
		l.line++
		l.column = 0
	}
}

func (l *Lexer) ReadTokens() []tok.Token {
	var tokens []tok.Token
	for {
		token := l.NextToken()
		tokens = append(tokens, token)
		if token.Type == tok.EOF {
			break
		}
	}
	return tokens
}

func (l *Lexer) NextToken() tok.Token {
	var token tok.Token

	l.skipSpace()

	// Check for special characters and delimiters
	switch l.ch {
	case '(':
		token = tok.Token{
			Type:    tok.LPAREN,
			Lexeme:  "(",
			Literal: "(",
			Line:    l.line,
			Column:  l.column,
		}
		l.advance()
	case ')':
		token = tok.Token{
			Type:    tok.RPAREN,
			Lexeme:  ")",
			Literal: ")",
			Line:    l.line,
			Column:  l.column,
		}
		l.advance()
	case '\'':
		token = tok.Token{
			Type:    tok.QUOTE,
			Lexeme:  "'",
			Literal: "'",
			Line:    l.line,
			Column:  l.column,
		}
		l.advance()
	case '`':
		token = tok.Token{
			Type:    tok.QUASIQUOTE,
			Lexeme:  "`",
			Literal: "`",
			Line:    l.line,
			Column:  l.column,
		}
		l.advance()
	case ',':
		// Check if it's unquote-splicing (,@) or just unquote (,)
		if l.peekChar() == '@' {
			token = tok.Token{
				Type:    tok.UNQUOTE_SPLICING,
				Lexeme:  ",@",
				Literal: ",@",
				Line:    l.line,
				Column:  l.column,
			}
			l.advance() // consume ','
			l.advance() // consume '@'
		} else {
			token = tok.Token{
				Type:    tok.UNQUOTE,
				Lexeme:  ",",
				Literal: ",",
				Line:    l.line,
				Column:  l.column,
			}
			l.advance()
		}
	case '"':
		token = l.readString()
	case ';':
		token = l.readComment()
		l.advance()
	case '#':
		p := l.peekChar()
		switch p {
		case 't', 'T':
			token = l.readBoolean(true)
		case 'f', 'F':
			token = l.readBoolean(false)
		case '\\':
			token = l.readCharacter()
		case '(':
			token = tok.Token{Type: tok.VECTOR,
				Lexeme:  "#(",
				Literal: "#(",
				Line:    l.line,
				Column:  l.column,
			}
			l.advance() // consume '#'
			l.advance() // consume '('
			return token
		default:
			token = tok.Token{Type: tok.ILLEGAL,
				Lexeme:  string(l.ch),
				Literal: string(l.ch),
				Line:    l.line,
				Column:  l.column,
			}
			l.advance()
			return token
		}
	case 0:
		token = tok.Token{
			Type:   tok.EOF,
			Line:   l.line,
			Column: l.column,
		}
	default:
		position := l.position
		for !isDelimiter(l.peekChar()) && !l.isEOF() {
			l.advance()
		}
		text := l.input[position:l.readPosition]

		// Try to parse as complex first, since it might contain rationals
		if t, err := l.readComplex(text); err == nil {
			token = t
		} else if t, err := l.readInteger(text); err == nil {
			token = t
		} else if t, err := l.readReal(text); err == nil {
			token = t
		} else if t, err := l.readRational(text); err == nil {
			token = t
		} else if t, err := l.readIdentifier(text); err == nil {
			token = t
		} else {
			token = tok.Token{
				Type:    tok.ILLEGAL,
				Lexeme:  text,
				Literal: text,
				Line:    l.line,
				Column:  l.column,
			}
		}

		l.advance()
	}

	return token
}

func (l *Lexer) skipSpace() {
	for isSpace(l.ch) {
		l.advance()
	}
}

func isDelimiter(ch byte) bool {
	return isSpace(ch) || ch == '(' || ch == ')' || ch == '\''
}

func isSpace(ch byte) bool {
	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}

func (l *Lexer) readBoolean(literal bool) tok.Token {
	token := tok.Token{
		Type:    tok.BOOLEAN,
		Lexeme:  l.input[l.position : l.position+2],
		Literal: literal,
		Line:    l.line,
		Column:  l.column + 1,
	}
	l.advance()
	l.advance()
	return token
}
func (l *Lexer) readCharacter() tok.Token {
	// Skip the #\
	l.advance() // Skip #
	l.advance() // Skip \

	// Handle special named characters
	if l.ch == 's' && l.peekString(4) == "pace" {
		// Skip the "pace"
		for i := 0; i < 4; i++ {
			l.advance()
		}
		token := tok.Token{
			Type:    tok.CHARACTER,
			Lexeme:  "#\\space",
			Literal: byte(' '),
			Line:    l.line,
			Column:  l.column,
		}
		l.advance() // Move past the last character
		return token
	} else if l.ch == 'n' && l.peekString(6) == "ewline" {
		// Skip the "ewline"
		for i := 0; i < 6; i++ {
			l.advance()
		}
		token := tok.Token{
			Type:    tok.CHARACTER,
			Lexeme:  "#\\newline",
			Literal: byte('\n'),
			Line:    l.line,
			Column:  l.column,
		}
		l.advance() // Move past the last character
		return token
	} else if l.ch == 't' && l.peekString(2) == "ab" {
		// Skip the "ab"
		for range 2 {
			l.advance()
		}
		token := tok.Token{
			Type:    tok.CHARACTER,
			Lexeme:  "#\\tab",
			Literal: byte('\t'),
			Line:    l.line,
			Column:  l.column,
		}
		l.advance() // Move past the last character
		return token
	}

	// Handle single character
	char := l.ch
	lexeme := "#\\" + string(char)
	l.advance()

	token := tok.Token{
		Type:    tok.CHARACTER,
		Lexeme:  lexeme,
		Literal: byte(char), // Ensure we're using byte type
		Line:    l.line,
		Column:  l.column - 1, // Adjust column to point to the character itself
	}

	return token
}

// Helper function to peek at the next n characters as a string
func (l *Lexer) peekString(n int) string {
	if l.readPosition+n > len(l.input) {
		return ""
	}
	return l.input[l.readPosition : l.readPosition+n]
}

func (l *Lexer) readInteger(text string) (tok.Token, error) {
	// Regular expression for integers: optional minus sign followed by digits
	// An integer can start with digit 0
	integerRegex := regexp.MustCompile(`^-?[0-9]+$`)
	if !integerRegex.MatchString(text) {
		return tok.Token{}, errors.New("invalid integer format")
	}

	// Parse the integer value
	val, err := parseIntValue(text)
	if err != nil {
		return tok.Token{}, err
	}

	token := tok.Token{
		Type:    tok.INTEGER,
		Lexeme:  text,
		Literal: val,
		Line:    l.line,
		Column:  l.column,
	}

	return token, nil
}

func parseIntValue(text string) (num.Number, error) {
	value, b := new(big.Int).SetString(text, 10)
	if !b {
		return nil, errors.New("invalid integer format")
	}

	var val num.Number
	if value.IsInt64() {
		val = num.Integer(value.Int64())
	} else {
		val = num.BigInteger(*value)
	}
	return val, nil
}

func (l *Lexer) readIdentifier(text string) (tok.Token, error) {
	t := tok.IDENTIFIER
	var err error = nil
	for _, ch := range []byte(text) {
		if !isLetter(ch) && !isDigit(ch) && !isSpecialInitialChar(ch) {
			t = tok.ILLEGAL
			err = errors.New("invalid identifier format")
		}
	}
	token := tok.Token{
		Type:    t,
		Lexeme:  text,
		Literal: text,
		Line:    l.line,
		Column:  l.column,
	}

	return token, err
}

func (l *Lexer) readString() tok.Token {
	position := l.position
	l.advance()
	for l.ch != '"' {
		if l.isEOF() {
			return tok.Token{
				Type:   tok.ILLEGAL,
				Lexeme: l.input[position:l.position],
				Line:   l.line,
				Column: l.column,
			}
		}
		l.advance()
	}
	literal := l.input[position+1 : l.position]
	// Advance past the closing quote
	l.advance()

	var s strings.Builder
	s.WriteByte('"')
	for _, ch := range literal {
		switch ch {
		case '\a':
			s.WriteString("\\a")
		case '\b':
			s.WriteString("\\b")
		case '\f':
			s.WriteString("\\f")
		case '\n':
			s.WriteString("\\n")
		case '\r':
			s.WriteString("\\r")
		case '\t':
			s.WriteString("\\t")
		case '\v':
			s.WriteString("\\v")
		case '\\':
			s.WriteString(`\\`)
		case '\'':
			s.WriteString(`\'`)
		case '"':
			s.WriteString(`\"`)
		default:
			s.WriteRune(ch)
		}
	}
	s.WriteByte('"')

	return tok.Token{
		Type:    tok.STRING,
		Lexeme:  s.String(),
		Literal: literal,
		Line:    l.line,
		Column:  l.column,
	}
}
func (l *Lexer) peekChar() byte {
	if l.readPosition >= len(l.input) {
		return 0
	}

	return l.input[l.readPosition]
}

func isLetter(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
}

func isDigit(ch byte) bool {
	return '0' <= ch && ch <= '9'
}

// ⟨special initial⟩ −→ ! | $ | % | & | * | / | : | < |=| > | ? |~ |_ |^
func isSpecialInitialChar(ch byte) bool {
	switch ch {
	case '!', '$', '%', '&', '*', '/', ':', '<', '=', '>', '?', '~', '_', '^', '.', '+', '-':
		return true
	default:
		return false
	}
}

func (l *Lexer) isEOF() bool {
	return l.readPosition >= len(l.input)
}

func (l *Lexer) readRational(text string) (tok.Token, error) {
	// Regular expression for rationals: optional minus sign followed by digits, a slash, and more digits
	rationalRegex := regexp.MustCompile(`^-?[0-9]+/[0-9]+$`)
	if !rationalRegex.MatchString(text) {
		return tok.Token{}, errors.New("invalid rational format")
	}

	// Split the text on the slash
	parts := strings.Split(text, "/")
	if len(parts) != 2 {
		return tok.Token{}, errors.New("invalid rational format")
	}

	// Parse numerator and denominator
	//numerator, err := strconv.ParseInt(parts[0], 10, 64)
	numerator, err := parseIntValue(parts[0])
	if err != nil {
		return tok.Token{}, err
	}

	//denominator, err := strconv.ParseInt(parts[1], 10, 64)
	denominator, err := parseIntValue(parts[1])
	if err != nil {
		return tok.Token{}, err
	}

	// Check for zero denominator
	if num.IsZero(denominator) {
		return tok.Token{}, errors.New("rational number cannot have zero denominator")
	}

	var token tok.Token
	r := new(big.Rat).SetFrac(num.ToBigInt(numerator), num.ToBigInt(denominator))
	rt := num.Rational(*r)
	if r.IsInt() {
		token = tok.Token{
			Type:    tok.INTEGER,
			Lexeme:  text,
			Literal: rt.Num(),
			Line:    l.line,
			Column:  l.column,
		}
	} else {
		token = tok.Token{
			Type:    tok.RATIONAL,
			Lexeme:  text,
			Literal: rt,
			Line:    l.line,
			Column:  l.column,
		}
	}

	return token, nil
}

func (l *Lexer) readReal(text string) (tok.Token, error) {
	// Regular expression for reals: optional minus sign followed by digits and a decimal point
	realRegex := regexp.MustCompile(`^-?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$`)
	if !realRegex.MatchString(text) {
		return tok.Token{}, errors.New("invalid real format")
	}

	// Parse the real value
	value, err := strconv.ParseFloat(text, 64)
	if err != nil {
		return tok.Token{}, err
	}

	token := tok.Token{
		Type:    tok.REAL,
		Lexeme:  text,
		Literal: num.Real(value),
		Line:    l.line,
		Column:  l.column,
	}

	return token, nil
}

func (l *Lexer) readComplex(text string) (tok.Token, error) {
	// Only handle actual complex numbers with 'i'
	if !strings.Contains(text, "i") {
		return tok.Token{}, errors.New("not a complex number")
	}

	// A standalone "i" should be treated as an identifier, not a complex number
	if text == "i" {
		return tok.Token{}, errors.New("standalone 'i' is an identifier, not a complex number")
	}

	// Handle special cases first (but not standalone "i")
	if text == "+i" || text == "i" {
		token := tok.Token{
			Type:    tok.COMPLEX,
			Lexeme:  text,
			Literal: num.Complex(complex(0, 1)),
			Line:    l.line,
			Column:  l.column,
		}
		return token, nil
	}

	if text == "-i" {
		token := tok.Token{
			Type:    tok.COMPLEX,
			Lexeme:  text,
			Literal: num.Complex(complex(0, -1)),
			Line:    l.line,
			Column:  l.column,
		}
		return token, nil
	}

	// Parse real and imaginary parts
	var realPart num.Number = num.Integer(0)
	var imagPart num.Number = num.Integer(0)

	// Handle the case where there's no real part (e.g., "3i", "+i", "-i")
	if strings.Count(text, "+")+strings.Count(text, "-") == 0 ||
		(strings.Count(text, "+")+strings.Count(text, "-") == 1 &&
			(text[0] == '+' || text[0] == '-')) {
		// Pure imaginary number
		imagStr := strings.TrimSuffix(text, "i")
		switch imagStr {
		case "", "+":
			imagPart = num.Integer(1)
		case "-":
			imagPart = num.Integer(-1)
		default:
			// Try to parse as signed rational first, then unsigned rational
			if r, err := l.tryParseSignedRational(imagStr); err == nil {
				imagPart = r
			} else if value, err := strconv.ParseInt(imagStr, 10, 64); err == nil {
				imagPart = num.Integer(value)
			} else if value, err := strconv.ParseFloat(imagStr, 64); err == nil {
				imagPart = num.Real(value)
			} else {
				return tok.Token{}, errors.New("invalid imaginary part in complex number")
			}
		}
	} else {
		// Complex number with both real and imaginary parts
		// Find the position of the last + or - before 'i'
		lastPlus := strings.LastIndex(text, "+")
		lastMinus := strings.LastIndex(text, "-")

		// Determine which operator is closer to the 'i'
		opIndex := lastPlus
		if lastMinus > lastPlus && lastMinus > 0 {
			opIndex = lastMinus
		}

		if opIndex <= 0 || opIndex >= len(text)-1 {
			return tok.Token{}, errors.New("invalid complex number format")
		}

		realStr := text[:opIndex]
		imagStr := text[opIndex : len(text)-1] // Exclude the 'i'

		// Parse real part
		if realStr != "" {
			// Try to parse as signed rational first, then unsigned rational
			if r, err := l.tryParseSignedRational(realStr); err == nil {
				realPart = r
			} else if r, err := l.tryParseRational(realStr); err == nil {
				realPart = r
			} else if value, err := strconv.ParseInt(realStr, 10, 64); err == nil {
				realPart = num.Integer(value)
			} else if value, err := strconv.ParseFloat(realStr, 64); err == nil {
				realPart = num.Real(value)
			} else {
				return tok.Token{}, errors.New("invalid real part in complex number")
			}
		}

		// Parse imaginary part
		switch imagStr {
		case "+":
			imagPart = num.Integer(1)
		case "-":
			imagPart = num.Integer(-1)
		default:
			// Try to parse as signed rational first, then unsigned rational
			if r, err := l.tryParseSignedRational(imagStr); err == nil {
				imagPart = r
			} else if r, err := l.tryParseRational(imagStr); err == nil {
				imagPart = r
			} else if value, err := strconv.ParseInt(imagStr, 10, 64); err == nil {
				imagPart = num.Integer(value)
			} else if value, err := strconv.ParseFloat(imagStr, 64); err == nil {
				imagPart = num.Real(value)
			} else {
				return tok.Token{}, errors.New("invalid imaginary part in complex number")
			}
		}
	}

	token := tok.Token{
		Type:    tok.COMPLEX,
		Lexeme:  text,
		Literal: num.Complex(complex(getFloat64(realPart), getFloat64(imagPart))),
		Line:    l.line,
		Column:  l.column,
	}

	return token, nil
}

// Helper function to extract float64 value from Number interface
func getFloat64(n num.Number) float64 {
	switch v := n.(type) {
	case num.Integer:
		return float64(v)
	case num.Real:
		return float64(v)
	case num.Rational:
		r := (*big.Rat)(&v)
		f, _ := r.Float64()
		return f
	default:
		// For other types, we might need a more sophisticated approach
		// This is a simplified version for now
		return 0.0
	}
}

// Helper function to try parsing a rational number
func (l *Lexer) tryParseRational(text string) (num.Number, error) {
	token, err := l.readRational(text)
	if err != nil {
		return nil, err
	}
	return token.Literal.(num.Number), nil
}

func (l *Lexer) tryParseSignedRational(text string) (num.Number, error) {
	switch text[0] {
	case '-':
		unsignedPart := text[1:]
		token, err := l.readRational(unsignedPart)
		if err != nil {
			return nil, err
		}
		rational := token.Literal.(num.Rational)
		negRat := rational.NEG()
		return negRat, nil
	case '+':
		unsignedPart := text[1:]
		token, err := l.readRational(unsignedPart)
		if err != nil {
			return nil, err
		}
		return token.Literal.(num.Number), nil
	default:
		return nil, fmt.Errorf("invalid signed rational: %s", text)
	}
}

func (l *Lexer) readComment() tok.Token {
	startPos := l.position

	// Skip the semicolon
	l.advance()

	// Read until end of line or EOF
	for !l.isEOF() && l.peekChar() != '\n' {
		l.advance()
	}

	// Extract the comment text (including the semicolon)
	commentText := l.input[startPos:l.readPosition]

	token := tok.Token{
		Type:    tok.COMMENT,
		Lexeme:  commentText,
		Literal: commentText,
		Line:    l.line,
		Column:  l.column,
	}
	if l.ch == '\n' {
		l.advance()
	}
	return token
}
